#include "camera.h"

#include <glm/gtc/matrix_transform.hpp>

// constructor with vectors
Camera::Camera(glm::vec3 position, glm::vec3 up, float yaw, float pitch)
    : front(glm::vec3(0.0f, 0.0f, -1.0f))
    , movementSpeed(SPEED)
    , mouseSensitivity(SENSITIVITY)
    , zoom(ZOOM) {
  this->position = position;
  this->worldUp = up;
  this->yaw = yaw;
  this->pitch = pitch;
  updateCameraVectors();
}
// constructor with scalar values
Camera::Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch)
    : front(glm::vec3(0.0f, 0.0f, -1.0f))
    , movementSpeed(SPEED)
    , mouseSensitivity(SENSITIVITY)
    , zoom(ZOOM) {
  this->position = glm::vec3(posX, posY, posZ);
  this->worldUp = glm::vec3(upX, upY, upZ);
  this->yaw = yaw;
  this->pitch = pitch;
  updateCameraVectors();
}

// returns the view matrix calculated using Euler Angles and the LookAt Matrix
glm::mat4 Camera::viewMatrix() {
  return glm::lookAt(position, position + front, up);
}

// processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems)
void Camera::processKeyboard(CameraMovement direction) {
  if (direction == CameraMovement::Forward)
    movement_ = front;
  else if (direction == CameraMovement::Backward)
    movement_ = -front;
  else if (direction == CameraMovement::Left)
    movement_ = -right;
  else if (direction == CameraMovement::Right)
    movement_ = right;
  else
    movement_ = glm::vec3(0.f, 0.f, 0.f);
}

// processes input received from a mouse input system. Expects the offset value in both the x and y direction.
void Camera::processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch) {
  xoffset *= mouseSensitivity;
  yoffset *= mouseSensitivity;

  yaw   += xoffset;
  pitch += yoffset;

  // make sure that when pitch is out of bounds, screen doesn't get flipped
  if (constrainPitch) {
    if (pitch > 89.0f)
      pitch = 89.0f;
    if (pitch < -89.0f)
      pitch = -89.0f;
  }

  // update Front, Right and Up Vectors using the updated Euler angles
  updateCameraVectors();
}

// processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis
void Camera::processMouseScroll(float yoffset) {
  zoom -= (float)yoffset;
  if (zoom < 1.0f)
    zoom = 1.0f;
  if (zoom > 45.0f)
    zoom = 45.0f; 
}

void Camera::update(float deltaTime) {
  position += movement_ * movementSpeed * deltaTime;
  movement_ = glm::vec3(0.f, 0.f, 0.f);
}

// calculates the front vector from the Camera's (updated) Euler Angles
void Camera::updateCameraVectors() {
  // calculate the new Front vector
  glm::vec3 tmp;
  tmp.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
  tmp.y = sin(glm::radians(pitch));
  tmp.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
  front = glm::normalize(tmp);
  // also re-calculate the Right and Up vector
  right = glm::normalize(glm::cross(front, worldUp));  // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
  up    = glm::normalize(glm::cross(right, front));
}